# -*- coding: utf-8 -*-
"""
彩票爬虫基类
提供通用的爬虫功能和接口
"""

import requests
import time
import json
from datetime import datetime, date
from typing import List, Dict, Any, Optional
from abc import ABC, abstractmethod
from loguru import logger
from bs4 import BeautifulSoup
import random

from ..database import db_manager
from ..config.settings import CRAWLER_CONFIG


class BaseLotterySpider(ABC):
    """彩票爬虫基类"""
    
    def __init__(self, lottery_type: str):
        self.lottery_type = lottery_type
        self.session = requests.Session()
        self.setup_session()
        
    def setup_session(self):
        """设置请求会话"""
        # 设置随机User-Agent
        user_agent = random.choice(CRAWLER_CONFIG['user_agents'])
        self.session.headers.update({
            'User-Agent': user_agent,
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
            'Accept-Encoding': 'gzip, deflate',
            'Connection': 'keep-alive',
            'Upgrade-Insecure-Requests': '1'
        })
        
    def make_request(self, url: str, method: str = 'GET', 
                    params: Dict = None, data: Dict = None, 
                    headers: Dict = None, timeout: int = None) -> Optional[requests.Response]:
        """发起HTTP请求"""
        try:
            if headers:
                self.session.headers.update(headers)
                
            timeout = timeout or CRAWLER_CONFIG['timeout']
            
            if method.upper() == 'GET':
                response = self.session.get(url, params=params, timeout=timeout)
            elif method.upper() == 'POST':
                response = self.session.post(url, data=data, params=params, timeout=timeout)
            else:
                raise ValueError(f"不支持的请求方法: {method}")
                
            response.raise_for_status()
            return response
            
        except requests.exceptions.RequestException as e:
            logger.error(f"请求失败 {url}: {e}")
            return None
    
    def save_prize_details(self, period: str, prize_info: Any) -> bool:
        """保存奖级详情信息到prize_details表
        
        Args:
            period: 期号
            prize_info: 奖级信息，可能是dict或list格式
            
        Returns:
            是否保存成功
        """
        try:
            # 处理不同格式的奖级信息
            prize_list = []
            
            if isinstance(prize_info, list):
                prize_list = prize_info
            elif isinstance(prize_info, dict):
                # 如果是字典格式，尝试提取列表
                if 'list' in prize_info:
                    prize_list = prize_info['list']
                elif 'data' in prize_info:
                    prize_list = prize_info['data']
                else:
                    # 如果字典本身就是奖级信息，转换为列表
                    prize_list = [prize_info]
            
            if not prize_list:
                logger.warning(f"期号 {period} 没有有效的奖级信息")
                return True
            
            # 先删除该期号的旧数据
            delete_sql = """
            DELETE FROM prize_details 
            WHERE lottery_type = :lottery_type AND period = :period
            """
            db_manager.execute_sql(delete_sql, {
                'lottery_type': self.lottery_type,
                'period': period
            })
            
            # 插入新的奖级数据
            insert_sql = """
            INSERT INTO prize_details 
            (lottery_type, period, prize_level, prize_level_num, winner_count, 
             single_amount, total_amount, prize_condition)
            VALUES 
            (:lottery_type, :period, :prize_level, :prize_level_num, :winner_count,
             :single_amount, :total_amount, :prize_condition)
            """
            
            saved_count = 0
            for i, prize in enumerate(prize_list):
                try:
                    # 解析奖级信息
                    prize_level = str(prize.get('prizename', '') or prize.get('name', '') or f'第{i+1}等奖')
                    prize_level_num = prize.get('prizelevel', i + 1)
                    winner_count = self.clean_numeric_value(prize.get('num', 0) or prize.get('winner_count', 0))
                    single_amount = self.clean_numeric_value(prize.get('money', 0) or prize.get('single_amount', 0))
                    
                    # 计算总金额
                    total_amount = None
                    if winner_count and single_amount:
                        total_amount = float(winner_count) * float(single_amount)
                    
                    # 中奖条件描述
                    prize_condition = str(prize.get('condition', '') or prize.get('description', '') or '')
                    
                    params = {
                        'lottery_type': self.lottery_type,
                        'period': period,
                        'prize_level': prize_level,
                        'prize_level_num': int(prize_level_num) if prize_level_num else i + 1,
                        'winner_count': int(winner_count) if winner_count else 0,
                        'single_amount': float(single_amount) if single_amount else 0,
                        'total_amount': float(total_amount) if total_amount else 0,
                        'prize_condition': prize_condition
                    }
                    
                    db_manager.execute_sql(insert_sql, params)
                    saved_count += 1
                    
                except Exception as e:
                    logger.error(f"保存奖级详情失败 {period} 第{i+1}个奖级: {e}, 数据: {prize}")
                    continue
            
            logger.info(f"保存奖级详情成功: {self.lottery_type} {period}, 共 {saved_count} 个奖级")
            return True
            
        except Exception as e:
            logger.error(f"保存奖级详情失败: {e}")
            return False
            
    def retry_request(self, url: str, method: str = 'GET', 
                     params: Dict = None, data: Dict = None,
                     headers: Dict = None, max_retries: int = None) -> Optional[requests.Response]:
        """带重试的请求"""
        max_retries = max_retries or CRAWLER_CONFIG['retry_times']
        
        for attempt in range(max_retries + 1):
            try:
                response = self.make_request(url, method, params, data, headers)
                if response:
                    return response
                    
            except Exception as e:
                logger.warning(f"第 {attempt + 1} 次请求失败: {e}")
                
            if attempt < max_retries:
                delay = CRAWLER_CONFIG['request_delay'] * (attempt + 1)
                logger.info(f"等待 {delay} 秒后重试...")
                time.sleep(delay)
                
        logger.error(f"请求最终失败，已重试 {max_retries} 次")
        return None
        
    def parse_json_response(self, response: requests.Response) -> Optional[Dict]:
        """解析JSON响应"""
        try:
            return response.json()
        except json.JSONDecodeError as e:
            logger.error(f"JSON解析失败: {e}")
            return None
            
    def parse_html_response(self, response: requests.Response) -> Optional[BeautifulSoup]:
        """解析HTML响应"""
        try:
            return BeautifulSoup(response.text, 'html.parser')
        except Exception as e:
            logger.error(f"HTML解析失败: {e}")
            return None
            
    def save_lottery_result(self, period: str, draw_date: date, 
                           red_numbers: List[str] = None,
                           blue_numbers: List[str] = None,
                           all_numbers: List[str] = None,
                           draw_time: datetime = None,
                           sales_amount: float = None,
                           pool_amount: float = None,
                           prize_info: Dict = None,
                           extra_data: Dict = None) -> bool:
        """保存开奖结果"""
        try:
            # 保存主要开奖结果
            sql = """
            INSERT INTO lottery_results 
            (lottery_type, period, draw_date, draw_time, red_numbers, blue_numbers, 
             all_numbers, sales_amount, pool_amount, prize_info, extra_data)
            VALUES 
            (:lottery_type, :period, :draw_date, :draw_time, :red_numbers, :blue_numbers,
             :all_numbers, :sales_amount, :pool_amount, :prize_info, :extra_data)
            ON DUPLICATE KEY UPDATE
            draw_date = VALUES(draw_date),
            draw_time = VALUES(draw_time),
            red_numbers = VALUES(red_numbers),
            blue_numbers = VALUES(blue_numbers),
            all_numbers = VALUES(all_numbers),
            sales_amount = VALUES(sales_amount),
            pool_amount = VALUES(pool_amount),
            prize_info = VALUES(prize_info),
            extra_data = VALUES(extra_data),
            updated_at = NOW()
            """
            
            params = {
                'lottery_type': self.lottery_type,
                'period': period,
                'draw_date': draw_date,
                'draw_time': draw_time,
                'red_numbers': json.dumps(red_numbers) if red_numbers else None,
                'blue_numbers': json.dumps(blue_numbers) if blue_numbers else None,
                'all_numbers': json.dumps(all_numbers) if all_numbers else json.dumps((red_numbers or []) + (blue_numbers or [])),
                'sales_amount': self.clean_numeric_value(sales_amount),
                'pool_amount': self.clean_numeric_value(pool_amount),
                'prize_info': json.dumps(prize_info) if prize_info else None,
                'extra_data': json.dumps(extra_data) if extra_data else None
            }
            
            db_manager.execute_sql(sql, params)
            
            # 保存奖级详情信息
            if prize_info and isinstance(prize_info, (dict, list)):
                self.save_prize_details(period, prize_info)
            
            logger.info(f"保存开奖结果成功: {self.lottery_type} {period}")
            return True
            
        except Exception as e:
            logger.error(f"保存开奖结果失败: {e}")
            return False
            
    def get_latest_period(self) -> Optional[str]:
        """获取最新期号"""
        try:
            sql = """
            SELECT period FROM lottery_results 
            WHERE lottery_type = :lottery_type 
            ORDER BY draw_date DESC, period DESC 
            LIMIT 1
            """
            
            result = db_manager.execute_sql(sql, {'lottery_type': self.lottery_type})
            if result:
                return result[0]['period']
            return None
            
        except Exception as e:
            logger.error(f"获取最新期号失败: {e}")
            return None
            
    def create_crawler_task(self, task_name: str, task_type: str,
                           start_period: str = None, end_period: str = None,
                           config_data: Dict = None) -> Optional[int]:
        """创建爬虫任务"""
        try:
            sql = """
            INSERT INTO crawler_tasks 
            (task_name, lottery_type, task_type, start_period, end_period, 
             status, config_data, start_time)
            VALUES 
            (:task_name, :lottery_type, :task_type, :start_period, :end_period,
             'running', :config_data, NOW())
            """
            
            params = {
                'task_name': task_name,
                'lottery_type': self.lottery_type,
                'task_type': task_type,
                'start_period': start_period,
                'end_period': end_period,
                'config_data': json.dumps(config_data) if config_data else None
            }
            
            db_manager.execute_sql(sql, params)
            
            # 获取插入的任务ID
            result = db_manager.execute_sql("SELECT LAST_INSERT_ID() as task_id")
            if result:
                return result[0]['task_id']
            return None
            
        except Exception as e:
            logger.error(f"创建爬虫任务失败: {e}")
            return None
            
    def update_crawler_task(self, task_id: int, status: str = None,
                           progress: int = None, success_count: int = None,
                           failed_count: int = None, error_message: str = None,
                           result_data: Dict = None) -> bool:
        """更新爬虫任务状态"""
        try:
            update_fields = []
            params = {'task_id': task_id}
            
            if status:
                update_fields.append("status = :status")
                params['status'] = status
                
            if progress is not None:
                update_fields.append("progress = :progress")
                params['progress'] = progress
                
            if success_count is not None:
                update_fields.append("success_count = :success_count")
                params['success_count'] = success_count
                
            if failed_count is not None:
                update_fields.append("failed_count = :failed_count")
                params['failed_count'] = failed_count
                
            if error_message:
                update_fields.append("error_message = :error_message")
                params['error_message'] = error_message
                
            if result_data:
                update_fields.append("result_data = :result_data")
                params['result_data'] = json.dumps(result_data)
                
            if status in ['success', 'failed', 'cancelled']:
                update_fields.append("end_time = NOW()")
                
            if not update_fields:
                return True
                
            sql = f"""
            UPDATE crawler_tasks 
            SET {', '.join(update_fields)}, updated_at = NOW()
            WHERE id = :task_id
            """
            
            db_manager.execute_sql(sql, params)
            return True
            
        except Exception as e:
            logger.error(f"更新爬虫任务失败: {e}")
            return False
            
    def format_period(self, period_str: str) -> str:
        """格式化期号"""
        # 子类可以重写此方法来处理特定的期号格式
        return period_str.strip()
        
    def parse_date(self, date_str: str) -> Optional[date]:
        """解析日期字符串"""
        try:
            # 清理日期字符串，去除可能的中文字符和多余空格
            cleaned_date = date_str.strip()
            
            # 去除常见的中文字符
            cleaned_date = cleaned_date.replace('(日)', '').replace('（日）', '')
            cleaned_date = cleaned_date.replace('(一)', '').replace('（一）', '')
            cleaned_date = cleaned_date.replace('(二)', '').replace('（二）', '')
            cleaned_date = cleaned_date.replace('(三)', '').replace('（三）', '')
            cleaned_date = cleaned_date.replace('(四)', '').replace('（四）', '')
            cleaned_date = cleaned_date.replace('(五)', '').replace('（五）', '')
            cleaned_date = cleaned_date.replace('(六)', '').replace('（六）', '')
            cleaned_date = cleaned_date.replace('星期一', '').replace('星期二', '').replace('星期三', '')
            cleaned_date = cleaned_date.replace('星期四', '').replace('星期五', '').replace('星期六', '').replace('星期日', '')
            cleaned_date = cleaned_date.strip()
            
            # 尝试多种日期格式
            date_formats = [
                '%Y-%m-%d',
                '%Y/%m/%d',
                '%Y年%m月%d日',
                '%m-%d',
                '%m/%d'
            ]
            
            for fmt in date_formats:
                try:
                    parsed_date = datetime.strptime(cleaned_date, fmt)
                    # 如果没有年份，使用当前年份
                    if fmt in ['%m-%d', '%m/%d']:
                        parsed_date = parsed_date.replace(year=datetime.now().year)
                    return parsed_date.date()
                except ValueError:
                    continue
                    
            logger.warning(f"无法解析日期: {date_str} (清理后: {cleaned_date})")
            return None
            
        except Exception as e:
            logger.error(f"日期解析错误: {e}")
            return None
            
    def clean_numeric_value(self, value: Any) -> Optional[float]:
        """清理数字值，去除逗号分隔符等格式字符"""
        if value is None:
            return None
            
        try:
            # 转换为字符串
            str_value = str(value).strip()
            if not str_value:
                return None
                
            # 去除逗号分隔符
            str_value = str_value.replace(',', '')
            
            # 去除其他可能的格式字符
            str_value = str_value.replace(' ', '')
            
            # 转换为浮点数
            return float(str_value)
            
        except (ValueError, TypeError) as e:
            logger.warning(f"数字值清理失败: {value} -> {e}")
            return None
    
    def validate_numbers(self, numbers: List[str]) -> bool:
        """验证号码格式"""
        # 子类可以重写此方法来验证特定彩种的号码格式
        return len(numbers) > 0 and all(num.isdigit() for num in numbers)
        
    @abstractmethod
    def crawl_latest(self) -> bool:
        """爬取最新开奖结果"""
        pass
        
    @abstractmethod
    def crawl_history(self, start_period: str = None, end_period: str = None) -> bool:
        """爬取历史开奖结果"""
        pass
        
    @abstractmethod
    def parse_lottery_data(self, response_data: Any) -> List[Dict]:
        """解析开奖数据"""
        pass
        
    def close(self):
        """显式关闭资源（推荐使用此方法而不是依赖__del__）"""
        if hasattr(self, 'session') and self.session:
            try:
                self.session.close()
                logger.debug(f"已关闭 {self.lottery_type} 的 session")
            except Exception as e:
                logger.warning(f"关闭 session 时出错: {e}")
            finally:
                self.session = None
    
    def __del__(self):
        """析构函数"""
        self.close()
    
    def __enter__(self):
        """支持上下文管理器"""
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """上下文管理器退出时清理资源"""
        self.close()
        return False